/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package grex;

import grex.Data.ArffTableModel;
import java.util.ArrayList;
import java.util.Collections;

/**
 *
 * @author rik
 */
public class Fuzzyficator {

    private ArffTableModel arff;    
    private ArrayList<ArrayList<Double>> fuzzyData = new ArrayList<ArrayList<Double>>();
    private ArrayList<ArrayList<Double>> colData = new ArrayList<ArrayList<Double>>();
    private ArrayList<double[]> orgData;

    public Fuzzyficator() {
        
    }

    
    public ArrayList<ArrayList<Double>> fuzzyfyData(ArrayList<double[]> data,boolean[] colTypes, int n){
        colData = transformToColLists(data);
        for(int col=0;col<colTypes.length-1;col++){
            if(colTypes[col])
                fuzzyData.addAll(fuzzyfyCol(colData.get(col), n));
            else
                fuzzyData.add(colData.get(col));
        }
        return fuzzyData;
    }
    
    private ArrayList<ArrayList<Double>> fuzzyfyCol(ArrayList<Double> column,int n){        
        //Calc starting medioids for kMeans
        ArrayList<Double> medioids = calcMedioids(column, n);
        //Calc membership centroids
        ArrayList<Double> centroids = kMeans(column, medioids);
        ArrayList<ArrayList<Double>> fuzzySets = new ArrayList<ArrayList<Double>>();  
        //create fuzzy sets
        for(int m=0;m<n;m++){
            ArrayList<Double> fuzzySet = new ArrayList<Double>();            
            
            for (int r = 0; r < column.size(); r++) {                
                double x = column.get(r);
                double fuzzyValue=0;
                if(m==0){//If we are fuzzying the first membership function
                   double m1 = centroids.get(0);
                   double m2 = centroids.get(1);
                    if (x<=m1)
                        fuzzyValue=1;
                    else if(x>m1 && x<m2)
                        fuzzyValue= (m2-x)/(m2-m1);
                    else
                        fuzzyValue=0;
                }else if(m==n-1){//If we are Fuzzying the last membership function
                    double mk = centroids.get(m);
                    double mk_1 = centroids.get(m-1);
                    if(x>=mk)
                        fuzzyValue = 1;
                    else if (x>mk_1 && x<mk)
                        fuzzyValue = (mk_1-x)/(mk_1-mk);
                    else
                        fuzzyValue = 0;
                }else{ //All middle membership functions
                    double mi = centroids.get(m);
                    double mi_1 = centroids.get(m-1);
                    double mi1 = centroids.get(m+1);
                    if(x<mi_1|| x>mi1)
                        fuzzyValue = 0;
                    else if(x==mi)
                        fuzzyValue = 1;
                    else if ( x > mi_1 && x <mi )
                        fuzzyValue = (mi_1-x)/(mi_1-mi);
                    else if ( x > mi && x <mi1 )
                        fuzzyValue = (mi1-x)/(mi1-mi);                        
                }
                fuzzySet.add(fuzzyValue);        
            }
            fuzzySets.add(fuzzySet);
        }                
  
        return fuzzySets;
    }

    public static void main(String[] args) {
        Fuzzyficator f = new Fuzzyficator();
        /*ArrayList<Double> col = new ArrayList<Double>();
        col.add(2.0);
        col.add(4.0);
        col.add(8.0);
        col.add(10.0);
        col.add(12.0);
        System.out.println("mean" + f.mean(col));
        System.out.println("medoids" + f.calcMinDistance(col, f.mean(col)));
        for (Double d : f.calcMedioids(col, 3)) {
            System.out.println("Medioid:" + d);
        }        
        for (Double d : f.kMeans(col, f.calcMedioids(col, 3))) {
            System.out.println("Centroids:" + d);
        }
        
       ArrayList<ArrayList<Double>>fuzzysets = f.fuzzyfyCol(col, 3);
       for(int s = 0;s<fuzzysets.size();s++){
           ArrayList<Double> set = fuzzysets.get(s);
           for(Double d:set)
            System.out.println("Fuzzy"+s+": " + d);
           System.out.println("");
       }*/
        System.out.println(0/3);
       System.out.println(1/3);
       System.out.println(2/3);
       System.out.println(3/3);
       System.out.println(4/3);
       System.out.println(5/3);
       System.out.println(6/3);
       System.out.println(7/3);
       System.out.println(8/3);
       System.out.println(9/3);
        
       ArrayList<double[]> dat= new ArrayList<double[]>();
       double[] tmp1 = {1, 2 ,1};
       double[] tmp2 = {1, 1 ,8};
       double[] tmp3 = {2, 6 ,2};
       double[] tmp4 = {2, 7 ,3};
       double[] tmp5 = {6, 9 ,6};
       dat.add(tmp1);
       dat.add(tmp2);
       dat.add(tmp3);
       dat.add(tmp4);
       dat.add(tmp5);
  /*   ArrayList<ArrayList<Double>> fd = f.fuzzyfyData(dat, 3);
            for(int s = 0;s<fd.size();s++){
           ArrayList<Double> set = fd.get(s);
           for(Double d:set)
            System.out.println("col"+s+"Fuzzy"+s+": " + d);
           System.out.println("");
       }*/
    }

    private ArrayList<Double> kMeans(ArrayList<Double> col, ArrayList<Double> initCentroids) {
        ArrayList<Double> centroids = (ArrayList<Double>) initCentroids.clone();
        ArrayList<Double> newCentroids;        
        boolean noChange = false;
        while (!noChange) {
            //init member lists
            ArrayList<ArrayList<Double>> members = new ArrayList<ArrayList<Double>>();
            for (int i = 0; i < initCentroids.size(); i++) {
                members.add(new ArrayList<Double>());
            }
            //assign membership
            for (Double d : col) {
                IResult r = calcMinDistance(centroids, d);
                members.get(r.index).add(d);
            }

            //Calc new centroids
            newCentroids = new ArrayList<Double>();
            for (ArrayList<Double> cMember : members) {
                newCentroids.add(mean(cMember));
            }
            
            //Check if time to stop
            for(int i = 0; i<centroids.size();i++){
                if((double)centroids.get(i)!=(double)newCentroids.get(i)){  
                    centroids=newCentroids;
                    noChange=false;
                    break;
                }
                noChange=true;
            }
        }        
        Collections.sort(centroids);
        return centroids;
    }

    private ArrayList<Double> calcMedioids(ArrayList<Double> column, int n) {
        ArrayList<Double> medioids = new ArrayList<Double>();
        ArrayList<Double> col = (ArrayList<Double>) column.clone();

        //Finds first medioid (the most central instance)
        IResult m1 = calcMinDistance(col, mean(col));
        medioids.add(m1.value);
        col.remove(m1.index);

        //Calc other mediods
        //calc minDistance (mD) to any medioid for remaining instance
        while (medioids.size() < n) {
            IResult cGain = new IResult(-1, -1000, -1); //Max gain of selecting instance
            for (int i = 0; i < col.size(); i++) {
                for (int j = 0; j < col.size(); j++) {
                    if (i != j) {
                        double dj = calcMinDistance(medioids, col.get(j)).result; //Calc min distance to any medioid
                        double wji = Math.abs(col.get(j) - col.get(i)); //Calc distance to current instance
                        double gain = dj - wji; //gain of selecting instance i and j. This could be done faster with a distance matrix
                        if (gain > cGain.result && !medioids.contains(col.get(i))) {
                            cGain.result = gain;
                            cGain.index = i;
                            cGain.value = col.get(i);
                        }
                    }
                }
            }
            //Add medioid
            medioids.add(cGain.value);
            //Remove medioid instance from col
            
            //col.remove(cGain.index);
        }
        return medioids;
    }

    private double mean(ArrayList<Double> data) {
        double mean = 0;
        for (Double d : data) {
            mean += d;
        }
        return mean / data.size();
    }

    private IResult calcMinDistance(ArrayList<Double> data, double point) {
        IResult r = new IResult(0, Double.MAX_VALUE);
        for (int d = 0; d < data.size(); d++) {
            double dDist = Math.abs(point - data.get(d));
            if (dDist < r.result) {
                r.result = dDist;
                r.index = d;
                r.value = data.get(d);
            }
        }
        return r;
    }

    private ArrayList<ArrayList<Double>> transformToColLists(ArrayList<double[]> data) {
        ArrayList<ArrayList<Double>> newData = new ArrayList<ArrayList<Double>>();
        int nrOfCols = data.get(0).length;
        int nrOfRows = data.size();
        for (int r = 0; r < nrOfCols; r++) {
            newData.add(new ArrayList<Double>());
        }
        for (int r = 0; r < nrOfRows; r++) {
            for (int c = 0; c < nrOfCols; c++) {
                newData.get(c).add(data.get(r)[c]);
            }
        }
        return newData;
    }

    private class IResult {

        public int index = -1;
        public double result = -1;
        public double value = -1;

        public IResult(int index, double result, double value) {
            this.index = index;
            this.result = result;
            this.value = value;
        }

        public IResult(int index, double result) {
            this.index = index;
            this.result = result;
        }

        public IResult() {

        }

        public String toString() {
            return "Index: " + index + "\tDistance: " + result;
        }
    }
}
